//////////////////////////////////////////////////////////////////////
// (c) 2003-2010 sonofdelphi
// MIT License
//////////////////////////////////////////////////////////////////////

/*
	---THE FRAMEWORK-----

	The options with their values will be stored as in an array of strings.
	The data-structures when combined will function like a very simple hash-table.
	If the ith position of m_sOptions holds the option,
		the ith position of m_asValue will hold the value supplied for the option, if any.
		the ith position of m_asHelp will hold the help for the option, if any

	This scheme allows for an efficient retrieval of argument-values.

		////////////////
		//MEMORY-VIEW///
		////////////////
Tables	Options			Values						Help		..
		[Opt]	[][][][][][][]..........[\0]	[][][][][][][]..........[\0]
		[Opt]	[][][][][][][]..........[\0]		..
		[Opt]	[][][][][][][]..........[\0]		..
			.	.	.	.	.	.
			.	.	.	.	.	.
			.	.	.	.	.	.
		[Opt]	[][][][][][][]..........[\0]

	This will be filled in appropriately while parsing the supplied options.

	For example, if we are supplied with "-c -d foo" in operate
	the contents of the memory will look like
		[c]		[0]
		[d]		[f][o][o][\0]

*/			////////////////////////
	
#include "ArgumentParser.h"

#include"StringFunctions.h"
#include"Helper.h"

using namespace std;

//Instantiate static members
const char  CArgumentParser::cArgumentSpecifier = ':';
const char  CArgumentParser::cOptionPrefix = '-';

bool CArgumentParser::isValueEssentialForOption(char cOption)
{
	//Refer to the original syntax specification
	int n=strlen(m_sSyntax);
	int i;
	
	for(i=0;i<n;i++){
		if(m_sSyntax[i]==cOption)
			break;
	}

	if(m_sSyntax[i+1]==cArgumentSpecifier)
		return true;
	return false;
}

int CArgumentParser::indexOfOption(char c)				//Is option present in current syntax-string?
{
	//Refers to the position in the "key" table
	int n=strlen(m_sOption);
	for(int i=0;i<n;i++)
		if(m_sOption[i]==c)
			return i;
	return -1;
}

void CArgumentParser::invalidateOption(char c)			//Remove option from syntax
{
	//Modifies the working copy of syntax, removes the option because we have already used it
	int n=strlen(m_sSyntaxWorkingCopy);
	int i;
	
	for(i=0;i<n;i++)
		if(m_sSyntaxWorkingCopy[i]==c)
			break;
	
	CStringFunctions::deleteAt(i, m_sSyntaxWorkingCopy);
}

int CArgumentParser::parse(int& optc, char **argv)
{
	int validOption=-1,len;
	int lastWord=0;
	
	//Analyze word by word
	for(int i=1;i<optc;++i){
			
		if(m_bWasThereMoreToFill){
				fillValueForOption(m_sOption[validOption],argv[i]);
				lastWord = i;
				m_bWasThereMoreToFill=false;
				m_bWasParseSuccess=true;
				continue;
		}

		m_bIsInOptionMode=false;
		len=strlen(argv[i]);
		for(int j=0;j<len;++j){
			if(!m_bIsInOptionMode){
				if(argv[i][j]==cOptionPrefix){
					//Found the start of an option
					m_bIsInOptionMode=true;
					lastWord = i;
					continue;
				}
				else{
					break;	//Ignore the current word as a non-option parameter
					//Bug?: Please correct. Unless it is at the end, we should not accept as per getopt specs
				}
			}

			validOption=indexOfOption(argv[i][j]);
			if(validOption<0){
					m_bWasParseSuccess=false;
					return lastWord;
			}
			m_abSupplied[validOption]=true; // Mark the option as "supplied"
					
			if(isValueEssentialForOption(m_sOption[validOption])){
					if(argv[i][j+1]){
						fillValueForOption(m_sOption[validOption],argv[i]+j+1);
						m_bIsInOptionMode=false;
						m_bWasParseSuccess=true;
					}
					else{
						m_bWasThereMoreToFill=true;//We have to wait for parameter
						m_bWasParseSuccess=false;//Only if we get the value can we mark success
					}
					break;
			}
		}//j-loop
	}//i-loop
	
	m_bWasParseSuccess=true;
	return lastWord;
}


CArgumentParser::CArgumentParser(char* strSyntax)
{
	int i;
	int nLength = strlen(strSyntax);
	vector<string> asEmptyList;

	m_bWasParseSuccess = true;
	m_bIsInOptionMode = false;
	m_bWasThereMoreToFill=false;

	m_sSyntax=new char[nLength+1];
	strcpy(m_sSyntax,strSyntax);	//Copy the syntax-specification
	
	//Make working-copy
	m_sSyntaxWorkingCopy= new char[nLength+1];
	strcpy(m_sSyntaxWorkingCopy, strSyntax);

	//Strip the working-copy down to just options
	//Just making sure that no duplicates exist etc
	CStringFunctions::makeLetterSet(m_sSyntaxWorkingCopy);
	CStringFunctions::removeLetter(' ',m_sSyntaxWorkingCopy);	
	CStringFunctions::removeLetter(cArgumentSpecifier,m_sSyntaxWorkingCopy);	

	m_nOptions=	strlen(m_sSyntaxWorkingCopy);
	m_sOption=new char [m_nOptions+1];
	strcpy(m_sOption,m_sSyntaxWorkingCopy);

	//Initialize value area
	m_asValue.resize(m_nOptions);
	for(i=0;i<m_nOptions;i++)
		m_asValue.push_back(asEmptyList);
	
	m_abSupplied = new bool[m_nOptions];
	for(i=0;i<m_nOptions;i++)
		m_abSupplied[i] = false;
	
	m_Helper=new CHelper(m_nOptions);
}

CArgumentParser::~CArgumentParser()
{
	if(m_sSyntax)			delete[] m_sSyntax;
	if(m_sSyntaxWorkingCopy)delete[] m_sSyntaxWorkingCopy;
	if(m_sOption)			delete[] m_sOption;
	if(m_Helper)			delete m_Helper;
	if(m_abSupplied)		delete m_abSupplied;
}


bool CArgumentParser::wasOptionSupplied(char cOption)
{
	int index=indexOfOption(cOption);
	if(index<0){
		throw "No such option.";
	}
	
	if(m_abSupplied[index])
		return true;
	return false;
}

char* CArgumentParser::getField(char cOption, int nCardinal)
{
	int index=indexOfOption(cOption);
	if(index<0){
		throw "No such option.";
	}

	if(m_asValue[index].size() > 0){
		return (char*)m_asValue[index][nCardinal].c_str();
	}
	return NULL;
}

char* CArgumentParser::getField(int nPos, char& cOption, int nSeq)
{
	if(nPos>=m_nOptions){ throw "Error getting field nPos";	}

	cOption=m_sOption[nPos];
	if(m_asValue[nPos].size() > 0){
		return (char*)(m_asValue[nPos][nSeq].c_str());
	}
	return NULL;
}

bool CArgumentParser::wasParseSuccessful()
{
	return m_bWasParseSuccess;
}

void CArgumentParser::fillValueForOption(char cOption,char* strValue)
{
	int index=indexOfOption(cOption);
	if(index<0){throw "No such option";}
	
	m_asValue[index].push_back(strValue);	
}

void CArgumentParser::fillHelpForOption(char cOption, char* strValue)
{
	int index=indexOfOption(cOption);
	if(index<0){throw "No such option.";}
	m_Helper->fillHelpForOption(index,cOption,strValue);
}

bool CArgumentParser::isValidOption(char cOption)
{
	if(indexOfOption(cOption)<0){
		return false;
	}
	return true;
}

int	CArgumentParser::getFieldCardinal(char cOption)
{
	int index=indexOfOption(cOption);
	if(index<0){throw "No such option";}

	return m_asValue[index].size();
}

